/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ts, ArkFile, AstTreeUtils } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoMisusedPromisesCheck');
const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-misused-promises-check.md",
    description: 'Disallow Promises in places not designed to handle them',
    messages: {
        conditional: 'Expected non-Promise value in a boolean conditional.',
        spread: 'Expected a non-Promise value to be spreaded in an object.',
        voidReturnArgument:
            'Promise returned in function argument where a void return was expected.',
        voidReturnAttribute:
            'Promise-returning function provided to attribute where a void return was expected.',
        voidReturnProperty:
            'Promise-returning function provided to property where a void return was expected.',
        voidReturnReturnValue:
            'Promise-returning function provided to return value where a void return was expected.',
        voidReturnVariable:
            'Promise-returning function provided to variable where a void return was expected.',
    },
    defaultOptions: { checksConditionals: true, checksSpreads: true, checksVoidReturn: true }
};

type Options =
    {
        checksConditionals?: boolean;
        checksSpreads?: boolean;
        checksVoidReturn?:
        | boolean
        | ChecksVoidReturnOptions;
    };

interface ChecksVoidReturnOptions {
    arguments?: boolean;
    attributes?: boolean;
    properties?: boolean;
    returns?: boolean;
    variables?: boolean;
}

function parseCheckVoidReturn(checksVoidReturn: boolean | ChecksVoidReturnOptions | undefined): ChecksVoidReturnOptions | false {
    switch (checksVoidReturn) {
        case false:
            return false;
        case true:
        case undefined:
            return {
                arguments: true,
                attributes: true,
                properties: true,
                returns: true,
                variables: true,
            };

        default:
            return {
                arguments: checksVoidReturn.arguments ?? true,
                attributes: checksVoidReturn.attributes ?? true,
                properties: checksVoidReturn.properties ?? true,
                returns: checksVoidReturn.returns ?? true,
                variables: checksVoidReturn.variables ?? true,
            };
    }
}

const LogicalTypes = [ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken]

export class NoMisusedPromisesCheck implements BaseChecker {
    codeFix?(arkFile: ArkFile, fixKey: string): boolean {
        throw new Error("Method not implemented.");
    }
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const matchFileCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [matchFileCb];
    }

    private createProgram(filePath: string): ts.Program {
        return ts.createProgram([filePath], {});
    }

    checkNoMisusedPromises(astTree: ts.SourceFile, option: Options, program: ts.Program): { line: number, colum: number, message: string, sourceCode: string, generic?: string }[] {
        let errorPositions: { line: number, colum: number, message: string, sourceCode: string, generic?: string }[] = [];
        const checksConditionals = option.checksConditionals;
        const checksSpreads = option.checksConditionals;
        const checksVoidReturn = parseCheckVoidReturn(option.checksVoidReturn);
        const checker = program.getTypeChecker();
        checkNode(astTree);

        function checkNode(node: ts.Node) {
            conditionalChecks(node);
            voidReturnChecks(node);
            spreadChecks(node);
            node.forEachChild(checkNode);
        }

        function conditionalChecks(node: ts.Node, parameters?: ts.NodeArray<ts.ParameterDeclaration>) {
            if (!checksConditionals) {
                return;
            }
            if (ts.isConditionalExpression(node)) {
                checkConditional(node.condition, true, parameters);
            }
            if (ts.isIfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
                checkConditional(node.expression, true, parameters);
            }
            if (ts.isForStatement(node) && node.condition) {
                checkConditional(node.condition, true, parameters);
            }
            if (ts.isBinaryExpression(node) && LogicalTypes.includes(node.operatorToken.kind)) {//逻辑运算符 && || !
                checkConditional(node, false, parameters);
            }
            if (ts.isPrefixUnaryExpression(node) && node.operator == ts.SyntaxKind.ExclamationToken) {//前缀运算符
                checkConditional(node.operand, true, parameters);
            }
            if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) {
                if (node.body && ts.isBlock(node.body)) {
                    for (const statement of node.body.statements) {
                        conditionalChecks(statement, node.parameters);
                    }
                    return;
                }
            }
        }

        function checkConditional(node: ts.Expression, isTestExpr = false, parameters?: ts.NodeArray<ts.ParameterDeclaration>) {
            if (ts.isParenthesizedExpression(node)) {
                checkConditional(node.expression, isTestExpr, parameters);
                return;
            }
            if (ts.isIdentifier(node)) {
                if (parameters && parameters.some(parameter => ts.isIdentifier(parameter.name) && node.text == parameter.name.text && parameter.type && ts.isTypeReferenceNode(parameter.type) && parameter.type.typeName.getText() == 'Promise')) {
                    parameters?.forEach(parameter => {
                        if (ts.isIdentifier(parameter.name) && node.text == parameter.name.text && parameter.type && ts.isTypeReferenceNode(parameter.type) && parameter.type.typeName.getText() == 'Promise') {
                            report('conditional', node);
                        }
                    });
                } else {
                    let aliasType = getVariableNode(node, true) as ts.TypeAliasDeclaration;
                    if (isAlwaysThenable(aliasType.type)) {
                        report('conditional', node);
                    }
                }
            }
            if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
                return;
            }
            if (ts.isBinaryExpression(node)) {
                if (node.operatorToken.getText() != '??' || isTestExpr) {
                    checkConditional(node.left, isTestExpr, parameters);
                }
                if (isTestExpr) {
                    checkConditional(node.right, isTestExpr, parameters);
                }
                return;
            }
            if (isAlwaysThenable(node)) {
                report('conditional', node);
            }
        }

        function isAlwaysThenable(node: ts.Node): boolean {
            const type = checker.getTypeAtLocation(node);
            return isThenableType(checker, type);
        }

        function isThenableType(checker: ts.TypeChecker, type: ts.Type): boolean {
            const regex = /Promise<.*?>|PromiseLike<.*?>/;
            const typeString = checker.typeToString(type);
            if (type.symbol && type.symbol.escapedName == 'Array') {
                return false;
            }
            return regex.test(typeString);

        }

        function returnsThenable(checker: ts.TypeChecker, node: ts.Node): boolean {
            if ((ts.isArrowFunction(node) || ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node))) {
                if (node.type && node.type.kind == ts.SyntaxKind.VoidKeyword) {
                    return false;
                }
                if (node.modifiers?.some(modifier => modifier.kind == ts.SyntaxKind.AsyncKeyword) || (node.type && ts.isTypeReferenceNode(node.type) && node.type.typeName.getText() == 'Promise')) {
                    return true;
                }
            }
            let callNode = getCallExpressionBoday(node);
            if (!callNode) {
                return false;
            }
            let location = checker.getTypeAtLocation(callNode);
            return checker.typeToString(location).includes('Promise');
        }

        function anySignatureIsThenableType(node: ts.Node, type: ts.Type): boolean {
            // 获取类型的所有签名（方法和属性）
            const signatures = [
                ...checker.getSignaturesOfType(type, ts.SignatureKind.Call),
                ...checker.getSignaturesOfType(type, ts.SignatureKind.Construct)
            ];

            // 检查每个签名的返回类型是否是 Thenable 类型
            for (const signature of signatures) {
                const returnType = checker.getReturnTypeOfSignature(signature);
                if (isThenableType(checker, returnType)) {
                    return true;
                }
            }

            // 检查类型的每个属性是否是 Thenable 类型
            const properties = type.getProperties();
            for (const property of properties) {
                const propertyType = checker.getTypeOfSymbolAtLocation(property, node);
                if (isThenableType(checker, propertyType)) {
                    return true;
                }
            }
            return false;
        }

        function report(message: string, reportnode: ts.Node) {
            let { line, character } = reportnode.getSourceFile().getLineAndCharacterOfPosition(reportnode.getStart());
            errorPositions.push({ line: line + 1, colum: character + 1, message: message, sourceCode: reportnode.getText() });
        }

        function unionTypeParts(type: ts.Type): ts.Type[] {
            if (type.isUnion()) {
                return type.types;
            }
            return [type];
        }

        function voidFunctionArguments(node: ts.CallExpression | ts.NewExpression): Set<number> {
            if (!node.arguments || node.arguments.length == 0) {
                return new Set<number>;
            }
            const thenableReturnIndices = new Set<number>();
            const voidReturnIndices = new Set<number>();
            let callNode = getCallExpressionBoday(node.expression);
            if (!callNode) {
                return new Set<number>;
            }
            const type = checker.getTypeAtLocation(callNode);
            for (const subType of unionTypeParts(type)) {
                // Standard function calls and `new` have two different types of signatures
                const signatures = ts.isCallExpression(node)
                    ? subType.getCallSignatures()
                    : subType.getConstructSignatures();
                for (const signature of signatures) {
                    for (const [index, parameter] of signature.parameters.entries()) {
                        const decl = parameter.valueDeclaration;
                        let type = checker.getTypeOfSymbolAtLocation(
                            parameter,
                            node.expression,
                        );

                        if (decl && ts.isParameter(decl) && decl.dotDotDotToken) {
                            if (decl.type && ts.isTypeReferenceNode(decl.type) && ts.isIdentifier(decl.type.typeName) && decl.type.typeName.escapedText == 'Array') {
                                type = checker.getTypeArguments((type as ts.TypeReference))[0];
                                for (let i = index; i < node.arguments.length; i++) {
                                    checkThenableOrVoidArgument(
                                        node,
                                        type,
                                        i,
                                        thenableReturnIndices,
                                        voidReturnIndices,
                                    );
                                }
                            } else if (decl.type && ts.isTupleTypeNode(decl.type)) {
                                const typeArgs = checker.getTypeArguments((type as ts.TypeReference));
                                for (
                                    let i = index;
                                    i < node.arguments.length && i - index < typeArgs.length;
                                    i++
                                ) {
                                    checkThenableOrVoidArgument(
                                        node,
                                        typeArgs[i - index],
                                        i,
                                        thenableReturnIndices,
                                        voidReturnIndices,
                                    );
                                }
                            }
                        } else {
                            checkThenableOrVoidArgument(
                                node,
                                type,
                                index,
                                thenableReturnIndices,
                                voidReturnIndices,
                            );
                        }
                    }
                }
            }

            for (const index of thenableReturnIndices) {
                voidReturnIndices.delete(index);
            }
            return voidReturnIndices;
        }

        function checkThenableOrVoidArgument(node: ts.CallExpression | ts.NewExpression, type: ts.Type, index: number, thenableReturnIndices: Set<number>, voidReturnIndices: Set<number>) {
            if (isThenableReturningFunctionType(node.expression, type)) {
                thenableReturnIndices.add(index);
            } else if (isVoidReturningFunctionType(node.expression, type)) {
                if (!thenableReturnIndices.has(index)) {
                    voidReturnIndices.add(index);
                }
            }
        }

        function isVoidReturningFunctionType(node: ts.Node, type: ts.Type): boolean {
            let hadVoidReturn = checker.typeToString(type) == 'void';
            for (const subType of unionTypeParts(type)) {
                for (const signature of subType.getCallSignatures()) {
                    const returnType = signature.getReturnType();
                    if (isThenableType(checker, returnType)) {
                        return false;
                    }
                    hadVoidReturn ||= isTypeFlagSet(returnType, ts.TypeFlags.Void);
                }
            }
            return hadVoidReturn;
        }

        function isThenableReturningFunctionType(node: ts.Node, type: ts.Type): boolean {
            for (const subType of unionTypeParts(type)) {
                if (anySignatureIsThenableType(node, subType)) {
                    return true;
                }
            }
            return false;
        }

        function isTypeFlagSet(type: ts.Type, flag: ts.TypeFlags): boolean {
            return (type.flags & flag) !== 0;
        }

        function checkArguments(node: ts.CallExpression | ts.NewExpression) {
            const voidArgs = voidFunctionArguments(node);
            if (voidArgs.size === 0 || !node.arguments?.entries()) {
                return;
            }

            for (const [index, argument] of node.arguments?.entries() ?? []) {
                if (!voidArgs.has(index)) {
                    continue;
                }
                if (returnsThenable(checker, argument)) {
                    report('voidReturnArgument', argument);
                }
            }
        }

        function getVariableNode(variable: ts.Node, isAliaType: boolean): ts.Node | false {
            let parentNode = variable.parent;
            while (parentNode) {
                if ((ts.isVariableDeclaration(parentNode) || ts.isFunctionDeclaration(parentNode)) && parentNode.type && parentNode.type && ts.isTypeReferenceNode(parentNode.type) && isAliaType) {
                    //引用类型声明
                    variable = parentNode.type.typeName;
                    isAliaType = false;
                }
                if ((ts.isBlock(parentNode) || ts.isSourceFile(parentNode)) && !isAliaType) {
                    for (const child of parentNode.statements) {
                        //全局变量
                        if (ts.isFunctionDeclaration(child) || ts.isVariableDeclaration(child) || ts.isTypeAliasDeclaration(child)) {
                            if (child.name && ts.isIdentifier(variable) && ts.isIdentifier(child.name) && child.name.text == variable.text) {
                                return child;
                            }
                        }
                        if (ts.isVariableStatement(child)) {
                            for (const declarat of child.declarationList.declarations) {
                                //方法内变量
                                if (ts.isFunctionDeclaration(declarat) || ts.isVariableDeclaration(declarat) || ts.isTypeAliasDeclaration(declarat)) {
                                    if (declarat.name && ts.isIdentifier(variable) && ts.isIdentifier(declarat.name) && declarat.name.text == variable.text) {
                                        return declarat;
                                    }
                                }
                            }
                        }
                    }
                }
                parentNode = parentNode.parent;
            }
            return false;
        }

        function checkProperty(node: ts.PropertyAssignment | ts.ShorthandPropertyAssignment | ts.MethodDeclaration) {
            let contextualType;
            if (ts.isPropertyAssignment(node)) {
                if (!node.initializer) {
                    return
                }
                let aliasType = getVariableNode(node, true);
                if (aliasType && ts.isTypeAliasDeclaration(aliasType)) {
                    let callNode;
                    if (ts.isTypeLiteralNode(aliasType.type)) {
                        aliasType.type.members.forEach(child => {
                            if (ts.isPropertySignature(child) && child.name.getText() == node.name.getText() && child.type) {
                                callNode = getCallExpressionBoday(child.type);
                            }
                        });
                    }
                    if (ts.isFunctionTypeNode(aliasType.type)) {
                        callNode = getCallExpressionBoday(aliasType.type.type);

                    }
                    if (!callNode) {
                        return;
                    }
                    contextualType = checker.getTypeAtLocation(callNode)
                }
                if (contextualType !== undefined
                    &&
                    isVoidReturningFunctionType(
                        node.initializer,
                        contextualType,
                    ) &&
                    returnsThenable(checker, node.initializer)
                ) {
                    report('voidReturnProperty', node.name);
                }
            } else if (ts.isShorthandPropertyAssignment(node)) {
                if (!node.name && !ts.isIdentifier(node.name)) {
                    return;
                }
                let variable = getVariableNode(node.name, false);
                if (!variable) {
                    return;
                }
                let aliasType = getVariableNode(node.name, true);
                if (aliasType && ts.isTypeAliasDeclaration(aliasType)) {
                    let callNode;
                    if (ts.isTypeLiteralNode(aliasType.type)) {
                        aliasType.type.members.forEach(child => {
                            if (ts.isPropertySignature(child) && child.name.getText() == node.name.getText() && child.type) {
                                callNode = getCallExpressionBoday(child.type);
                            }
                        });
                    }
                    if (ts.isFunctionTypeNode(aliasType.type)) {
                        callNode = getCallExpressionBoday(aliasType.type.type);
                    }
                    if (!callNode) {
                        return;
                    }
                    contextualType = checker.getTypeAtLocation(callNode)
                }
                let check_node = ts.isVariableDeclaration(variable) ? variable.initializer : variable;
                if (contextualType !== undefined && check_node != undefined
                    &&
                    isVoidReturningFunctionType(check_node, contextualType) &&
                    returnsThenable(checker, check_node)
                ) {
                    report('voidReturnProperty', node.name);
                }
            } else if (ts.isMethodDeclaration(node)) {
                if (ts.isComputedPropertyName(node.name)) {
                    return;
                }
                const obj = node.parent;
                if (!ts.isObjectLiteralExpression(obj)) {
                    return;
                }
                if (!returnsThenable(checker, node)) {
                    return;
                }
                const objType = checker.getContextualType(obj);
                if (objType === undefined) {
                    return;
                }
                const propertySymbol = checker.getPropertyOfType(
                    objType,
                    node.name.text,
                );
                if (propertySymbol === undefined) {
                    return;
                }
                const contextualType = checker.getTypeOfSymbolAtLocation(
                    propertySymbol,
                    node.name,
                );
                if (isVoidReturningFunctionType(node.name, contextualType)) {
                    report('voidReturnProperty', node.name);
                }
                return;
            }
        }

        function isReturnVoidMakeProgres(node: ts.ReturnStatement): boolean {
            let isReturnVoid = false;
            function checkReturnVoid(node: ts.Node) {
                let returnParent = node.parent;
                if (ts.isMethodDeclaration(returnParent) || ts.isFunctionTypeNode(returnParent) || ts.isArrowFunction(returnParent) || ts.isFunctionDeclaration(returnParent)) {
                    if (returnParent.type && ts.isFunctionTypeNode(returnParent.type) && returnParent.type.type.kind == ts.SyntaxKind.VoidKeyword) {
                        isReturnVoid = true;
                    }
                    return;
                }
                checkReturnVoid(returnParent);
            }
            checkReturnVoid(node);
            return isReturnVoid;
        }

        function checkReturnStatement(node: ts.ReturnStatement, parameters?: ts.ParameterDeclaration[]) {
            if (node.expression === undefined) {
                return;
            }
            let expression: ts.Node = node.expression;
            if (ts.isArrowFunction(expression) && parameters) {
                isReturnVoid(expression, parameters);
                return;
            }
            if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && parameters) {
                if (parameters.some(parameter => ts.isIdentifier(parameter.name) && ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && parameter.name.text == expression.expression.text)) {
                    for (const parameter of parameters) {
                        if (ts.isIdentifier(parameter.name) && ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && parameter.name.text == expression.expression.text) {
                            if (parameter.type && ts.isFunctionTypeNode(parameter.type)) {
                                expression = parameter.type;
                            }
                        }
                    }
                }
            }
            if (isReturnVoidMakeProgres(node) && returnsThenable(checker, expression)) {
                report('voidReturnReturnValue', node);
            }
        }

        function isReturnVoid(node: ts.Node, parameters: ts.ParameterDeclaration[] = []) {
            if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isArrowFunction(node)) {
                if (node.type && (ts.isFunctionDeclaration(node.type) || ts.isMethodDeclaration(node.type) || ts.isArrowFunction(node.type)) && node.type.type && node.type.type.kind == ts.SyntaxKind.VoidKeyword) {
                    if (node.body && ts.isBlock(node.body) && node.parameters.length > 0) {
                        node.parameters.forEach(item => {
                            parameters.push(item)
                        });
                        for (const statement of node.body.statements) {
                            if (ts.isReturnStatement(statement)) {
                                checkReturnStatement(statement, parameters);
                            }
                        }
                    }
                }
                return;
            }
        }

        function voidReturnChecks(node: ts.Node) {
            if (!checksVoidReturn) {
                return;
            }
            if (checksVoidReturn.arguments && ts.isCallOrNewExpression(node)) {
                checkArguments(node);
            }
            if (checksVoidReturn.attributes && ts.isJsxAttribute(node)) {
                //todo checkJSXAttribute
            }
            if (checksVoidReturn.properties && (ts.isPropertyAssignment(node) || ts.isMethodDeclaration(node) || ts.isShorthandPropertyAssignment(node))) {
                checkProperty(node);
            }
            if (checksVoidReturn.returns) {
                if (ts.isReturnStatement(node)) {
                    checkReturnStatement(node);
                }
                isReturnVoid(node);

            }
            if (checksVoidReturn.variables) {
                if (ts.isBinaryExpression(node)) {
                    checkAssignment(node);
                }
                if (ts.isVariableDeclaration(node)) {
                    checkVariableDeclaration(node);
                }
            }
        }

        function checkAssignment(node: ts.BinaryExpression) {
            const varType = checker.getTypeAtLocation(node.left);
            if (!isVoidReturningFunctionType(node.left, varType)) {
                return;
            }
            if (returnsThenable(checker, node.right)) {
                report('voidReturnVariable', node.right);
            }
        }

        function checkVariableDeclaration(node: ts.VariableDeclaration) {
            if (!node.initializer) {
                return;
            }
            let check_node: ts.Node = node.name;
            if (node.type && ts.isFunctionTypeNode(node.type)) {
                check_node = node.type;
            }
            let callNode = getCallExpressionBoday(check_node);
            if (!callNode) {
                return;
            }
            const varType = checker.getTypeAtLocation(callNode);

            if (!isVoidReturningFunctionType(node.initializer, varType)) {
                return;
            }
            if (returnsThenable(checker, node.initializer)) {
                report('voidReturnVariable', node);
            }
        }

        function spreadChecks(node: ts.Node) {
            if (!checksSpreads) {
                return;
            }
            if (ts.isSpreadElement(node) || ts.isSpreadAssignment(node)) {
                checkSpread(node);
            }
        }

        function checkSpread(node: ts.SpreadElement | ts.SpreadAssignment) {
            if (isSometimesThenable(checker, node.expression)) {
                report('spread', node.expression);
            }
        }

        function isSometimesThenable(checker: ts.TypeChecker, node: ts.Node): boolean {
            let callNode = getCallExpressionBoday(node);
            if (!callNode) {
                return false;
            }
            const type = checker.getTypeAtLocation(callNode);
            for (const subType of unionTypeParts(checker.getApparentType(type))) {
                if (isThenableType(checker, subType)) {
                    return true;
                }
            }
            return false;
        }

        function getCaseStatement(node: ts.Node): ts.Node[] {
            let caseStatement: ts.Node[] = [];
            function checkReturnStatement(node: ts.Node) {
                if (ts.isReturnStatement(node)) {
                    caseStatement.push(node);
                }
                node.forEachChild(checkReturnStatement);
            }
            checkReturnStatement(node);
            return caseStatement;
        }

        function getCallExpressionBoday(node: ts.Node): ts.Node | false {
            if (ts.isParenthesizedExpression(node) || ts.isExpressionWithTypeArguments(node)) {
                return getCallExpressionBoday(node.expression);
            }
            if (ts.isTypeReferenceNode(node) && node.typeName.getText() == "Record" && node.typeArguments?.length == 2) {
                return getCallExpressionBoday(node.typeArguments[1])
            }
            if (ts.isObjectLiteralExpression(node)) {
                return getCallExpressionBoday(node.properties[0]);
            }
            if (ts.isTypeLiteralNode(node)) {
                if (node.members.length > 0) {
                    return getCallExpressionBoday(node.members[0]);
                }
                return false;
            }
            if (ts.isAwaitExpression(node)) {
                return false;
            }
            if (ts.isFunctionTypeNode(node) && node.type) {
                return getCallExpressionBoday(node.type);
            }
            if (ts.isBlock(node)) {
                let caseStatements = getCaseStatement(node);
                for (const caseStatement of caseStatements) {
                    if (ts.isReturnStatement(caseStatement) &&
                        caseStatement.expression && (!ts.isLiteralExpression(caseStatement.expression) && !(ts.isPrefixUnaryExpression(caseStatement.expression) && ts.isLiteralExpression(caseStatement.expression.operand)))
                    ) {
                        return getCallExpressionBoday(caseStatement);
                    }
                }
            }
            if (ts.isReturnStatement(node) && node.expression) {
                return getCallExpressionBoday(node.expression);
            }
            if (ts.isArrowFunction(node) && !node.type) {
                return getCallExpressionBoday(node.body);
            }
            if (ts.isMethodDeclaration(node) && node.body) {
                for (const statement of node.body.statements) {
                    if (ts.isReturnStatement(statement) && statement.expression) {
                        return getCallExpressionBoday(statement.expression)
                    }
                }
                return node.name;
            }
            if (ts.isConditionalExpression(node) || ts.isBinaryExpression(node)) {
                for (const child of node.getChildren()) {
                    if (child.getText().includes("Promise")) {
                        return getCallExpressionBoday(child);
                    }
                }
            }
            if (ts.isCallExpression(node)) {
                return node.expression;
            }
            return node;
        }
        return errorPositions;
    }

    private getOption(): Options {
        let option = this.metaData.defaultOptions;
        if (this.rule && this.rule.option[0]) {
            option = this.rule.option[0] as Options;
        }
        return option;
    }

    public check = (target: ArkFile) => {
        let astTree = AstTreeUtils.getASTNode(target.getName(), target.getCode());
        let options = this.getOption();
        let program = this.createProgram(target.getFilePath());
        let errorPositions: { line: number, colum: number, message: string, sourceCode: string, generic?: string }[]
            = this.checkNoMisusedPromises(astTree, options, program);
        errorPositions.forEach(position => {
            this.addIssueReport(target, position.line, position.colum, position.sourceCode, position.message, position.generic);
        });
    }

    private addIssueReport(arkFile: ArkFile, lineNum: number, startColum: number, code: string, messageId: string, generic: string = '') {
        const severity = this.rule.alert ?? this.metaData.severity;
        let message = this.metaData.messages[messageId];
        let filePath = arkFile.getFilePath();
        let endColum = startColum + code.length - 1;
        const fixKey = lineNum + '%' + startColum + '%' + endColum + '%' + this.rule.ruleId;
        const mergeKey = filePath + '%' + fixKey + '%' + message;
        let defect = new Defects(lineNum, startColum, message, severity, this.rule.ruleId, mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey)
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}